#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

#include "sedona.h"


#undef PAGE_SIZE
#undef PAGE_MASK


#define PAGE_SHIFT		12
#define PAGE_SIZE  		(1<<PAGE_SHIFT)		/* 4096byte Per Mapped Memory page */
#define PAGE_MASK 		(~(PAGE_SIZE-1) )

#define GPIO_EndAddr	0x560000DC
#define GPIO_StartAddr	0x56000000

#define CLKREG_StartAddr	0x4C000000 
#define CLKREG_EndAddr	0x4C000018 
#define CLKCON 		0x0C

#define REG(Addr)	(*(volatile unsigned int*) (Addr))

static char* GPIOConStatus[4] = {"INPUT", "OUTPUT", "SpecialUse", "Reserved"};
static char* GPIODataStatus[2] = {"LOW", "HIGH"};
static char* GPIOPullupStatus[2] = {"Enabled", "Disabled"};
int g_fd=-1, g_opt = -1, g_Bank_num=-1, g_Addr_offset=-1, g_MaskBit=-1, g_Function=-1, g_to=-1,g_pullup=-1;
void* g_GpioBase = NULL;
void* g_CLKBASE = NULL;



/* enable PCLK(50Mhz) to GPIO Block */
static void s3c2440_clkcon_init(void*  REG_CLK)
{
	REG((unsigned int)REG_CLK + CLKCON) |=0x2000;	
}

static void s3c2440_gpio_cfgpin(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit, unsigned int Function)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);	
	
	if(Addr_offset>0)
	{
		Function &= 3;
		tmp &= ~(3<<MaskBit);		/*used for GPIO Bank B-J, which use the 2 bits Mask*/
	}
	else	
	{
		Function &= 1;
		tmp &= ~(1<<MaskBit);		/*used for GPIO Bank A, which use the 1 bit Mask*/
	}
	
	tmp|= (Function<<MaskBit);	
	REG((unsigned int)Regaddr +Addr_offset)=tmp;
}

static unsigned int s3c2440_gpio_getcfg(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);		
	tmp >>= MaskBit;
	
	if(Addr_offset>0x0c)
		tmp &= 3;	/*used for GPIO Bank B-J, which use the 2 bits Mask*/
	else
		tmp &= 1;	/*used for GPIO Bank A, which use the 1 bit Mask*/

	return tmp;
}

static void s3c2440_gpio_setpin(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit, unsigned int to)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);	
	tmp &= ~(1<<MaskBit);	
	tmp |= (!!to<<MaskBit);
	REG((unsigned int)Regaddr +Addr_offset)=tmp;	
}

static unsigned int s3c2440_gpio_getpin(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);
	tmp &= (1<<MaskBit);	
	return tmp?1:0;
}

static void s3c2440_gpio_pullup(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit, unsigned int to)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);	
	tmp &= ~(1<<MaskBit);
	tmp |= !!to << MaskBit;
	REG((unsigned int)Regaddr +Addr_offset)=tmp;
}

static unsigned int s3c2440_gpio_getpull(void *Regaddr,  unsigned int Addr_offset, unsigned int MaskBit)
{
	unsigned int tmp=0;
	
	tmp=REG((unsigned int)Regaddr +Addr_offset);
	tmp &= (1<<MaskBit);	
	return tmp?1:0;
}

static int open_mem(){
        int fd =-1;
        fd = open("/dev/mem", O_RDWR | O_SYNC);
	if(fd<0)
	{
           fprintf(stderr, "open /dev/mem error\n");
	   exit(1);
		}
         return fd;
}

static void setmap(){
              g_CLKBASE = mmap(NULL, (CLKREG_EndAddr-CLKREG_StartAddr), PROT_READ|PROT_WRITE, MAP_SHARED,g_fd, CLKREG_StartAddr & PAGE_MASK);
		if(g_CLKBASE == NULL || g_CLKBASE == MAP_FAILED)
		{
			fprintf(stderr, "[Error]: S3C2440 CLKCON base mmap error\n");
			
		}
		//fprintf(stdout, "Re-Maped 0x%X to Space Addr:0x%X, Maped Size 0x%X bytes\n",CLKREG_StartAddr,CLKBASE ,(CLKREG_EndAddr-CLKREG_StartAddr));
		
		g_GpioBase = mmap(NULL, (GPIO_EndAddr-GPIO_StartAddr), PROT_READ|PROT_WRITE, MAP_SHARED,g_fd, GPIO_StartAddr & PAGE_MASK);
		if(g_GpioBase == NULL || g_GpioBase == MAP_FAILED)
		{
			fprintf(stderr, "S3C2440 gpio base mmap error\n");
		}

}

static int init(){
          g_fd = open_mem();
          setmap();
          s3c2440_clkcon_init(g_CLKBASE);
}



static void unmap(){
	munmap(g_GpioBase,(GPIO_EndAddr-GPIO_StartAddr)); 
	munmap(g_CLKBASE,(CLKREG_EndAddr-CLKREG_StartAddr));
        g_GpioBase = NULL;
        g_CLKBASE = NULL;
}

static void closedown(){
    unmap();
    close(g_fd);
    exit(0);
}

void  failed(){
	g_Bank_num = -1;
	g_Addr_offset = -1;
	g_MaskBit = -1;
	g_Function = -1,
	g_to = -1;

	munmap(g_GpioBase,(GPIO_EndAddr-GPIO_StartAddr)); 
	munmap(g_CLKBASE,(CLKREG_EndAddr-CLKREG_StartAddr));
	close(g_fd);
	g_GpioBase = NULL;
	g_CLKBASE = NULL;
	exit(1);
}


static void s3c2440_gpio_operation(int bank_num, int mask_bit, int function, int value){
        
   //locate the bank number -- the port A to J
   g_Bank_num = bank_num;				
   if( g_Bank_num < 0 || g_Bank_num > 8){
						
            fprintf(stderr, "[Error]: Please input correct Bank index num, the Bank index num should be 0--8 !\n");
            failed();
				}
   g_Addr_offset = g_Bank_num * 0x10;
   if(g_Addr_offset == 0x80)  g_Addr_offset += 0x50;

   //locate the pin of the bank
   g_MaskBit = mask_bit;
   if( g_MaskBit < 0 || g_MaskBit > 25){
	 fprintf(stderr, "[Error]: Please input correct GPIO index num, the GPIO index num should be 0--25(max) !\n");
         failed();
		}

   //config the gpio_config_reg
   g_Function = function;
   if( g_Function < 0 || g_Function > 3){
		fprintf(stderr, "[Error]: Please input correct 'Config_reg' value, the value should be 0x00--0x03 !\n");
                failed();
		}

    if(g_Bank_num>0)
		{
		s3c2440_gpio_cfgpin(g_GpioBase,g_Addr_offset,g_MaskBit*2,g_Function);
		fprintf(stdout,"...Writing the 'Config_reg' to %s...\n",GPIOConStatus[g_Function]);
	         }					
     else
	       {
               s3c2440_gpio_cfgpin(g_GpioBase,g_Addr_offset,g_MaskBit,g_Function);
	       fprintf(stdout,"...Writing the 'Config_reg' to %s...\n",GPIOConStatus[g_Function+1]);
						}
						
  //write to the gpio register

       g_to = value;					
       if( g_to < 0 || g_to > 1){
					
              fprintf(stderr, "[Error]: Please input correct 'Data_reg' value, the value should be 0x00--0x01 !\n");
              failed();
             }										
						
       s3c2440_gpio_setpin(g_GpioBase, g_Addr_offset+4, g_MaskBit, g_to);
       fprintf(stdout,"...Writing the 'Data_reg' to %s...\n",GPIODataStatus[g_to]);						

}


Cell gpio_GpioControl_doInit(SedonaVM* vm, Cell* params)
{
	init();
}

Cell gpio_GpioControl_doClose(SedonaVM* vm, Cell* params)
{
	closedown();
}


Cell gpio_GpioControl_gpioControl(SedonaVM* vm, Cell* params)
{
	 int32_t bank_num  = params[1].ival;
	 int32_t mask_bit  = params[2].ival;
	 int32_t function  = params[3].ival;
	 int32_t value     = params[4].ival;


   	s3c2440_gpio_operation(bank_num,mask_bit,function,value); 

   //set GPB5 = 0 , output
//   s3c2440_gpio_operation(1,6, 1, 0); //set GPB6 = 0 , output
//   s3c2440_gpio_operation(1,7, 1, 0); //set GPB7 = 0 , output
//   s3c2440_gpio_operation(1,8, 1, 0); //set GPB8 = 0 , output

//	s3c2440_gpio_operation(8,0, 1, 0); //set GPB5 = 0 , output

  

}
